/**
 * $Id: EdgeStyle.java,v 1.47 2011-09-06 19:43:50 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.kg.rails.component.editor.view;

import com.kg.rails.component.editor.model.Geometry;
import com.kg.rails.component.editor.model.IGraphModel;
import com.kg.rails.component.editor.util.Constants;
import com.kg.rails.component.editor.util.EditorPoint;
import com.kg.rails.component.editor.util.EditorUtils;

import java.util.List;

/**
 * Provides various edge styles to be used as the values for
 * Constants.STYLE_EDGE in a cell style. Alternatevly, the Constants.
 * EDGESTYLE_* constants can be used to reference an edge style via the
 * StyleRegistry.
 */
public class EdgeStyle {

    /**
     * Defines the requirements for an edge style function.
     */
    public interface mxEdgeStyleFunction {

        /**
         * Implements an edge style function. At the time the function is called, the result
         * array contains a placeholder (null) for the first absolute point,
         * that is, the point where the edge and source terminal are connected.
         * The implementation of the style then adds all intermediate waypoints
         * except for the last point, that is, the connection point between the
         * edge and the target terminal. The first ant the last point in the
         * result array are then replaced with mxPoints that take into account
         * the terminal's perimeter and next point on the edge.
         *
         * @param state  Cell state that represents the edge to be updated.
         * @param source Cell state that represents the source terminal.
         * @param target Cell state that represents the target terminal.
         * @param points List of relative control points.
         * @param result Array of points that represent the actual points of the
         *               edge.
         */
        void apply(CellState state, CellState source, CellState target,
                   List<EditorPoint> points, List<EditorPoint> result);

    }

    /**
     * Provides an entity relation style for edges (as used in database
     * schema diagrams).
     */
    public static mxEdgeStyleFunction EntityRelation = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.kg.rails.component.editor.view.EdgeStyle.mxEdgeStyleFunction#apply(com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, java.util.List, java.util.List)
           */
        public void apply(CellState state, CellState source,
                          CellState target, List<EditorPoint> points, List<EditorPoint> result) {
            GraphView view = state.getView();
            IGraphModel model = view.getGraph().getModel();
            double segment = EditorUtils.getDouble(state.getStyle(),
                    Constants.STYLE_SEGMENT, Constants.ENTITY_SEGMENT)
                    * state.view.getScale();

            EditorPoint p0 = state.getAbsolutePoint(0);
            EditorPoint pe = state
                    .getAbsolutePoint(state.getAbsolutePointCount() - 1);

            boolean isSourceLeft = false;

            if (p0 != null) {
                source = new CellState();
                source.setX(p0.getX());
                source.setY(p0.getY());
            } else if (source != null) {
                int constraint = EditorUtils.getPortConstraints(source, state, true, Constants.DIRECTION_MASK_NONE);

                if (constraint != Constants.DIRECTION_MASK_NONE) {
                    isSourceLeft = constraint == Constants.DIRECTION_MASK_WEST;
                } else {
                    Geometry sourceGeometry = model.getGeometry(source.cell);

                    if (sourceGeometry.isRelative()) {
                        isSourceLeft = sourceGeometry.getX() <= 0.5;
                    } else if (target != null) {
                        isSourceLeft = target.getX() + target.getWidth() < source
                                .getX();
                    }
                }
            }

            boolean isTargetLeft = true;

            if (pe != null) {
                target = new CellState();
                target.setX(pe.getX());
                target.setY(pe.getY());
            } else if (target != null) {
                int constraint = EditorUtils.getPortConstraints(target, state, false, Constants.DIRECTION_MASK_NONE);

                if (constraint != Constants.DIRECTION_MASK_NONE) {
                    isTargetLeft = constraint == Constants.DIRECTION_MASK_WEST;
                } else {
                    Geometry targetGeometry = model.getGeometry(target.cell);

                    if (targetGeometry.isRelative()) {
                        isTargetLeft = targetGeometry.getX() <= 0.5;
                    } else if (source != null) {
                        isTargetLeft = source.getX() + source.getWidth() < target
                                .getX();
                    }
                }
            }

            if (source != null && target != null) {
                double x0 = (isSourceLeft) ? source.getX() : source.getX()
                        + source.getWidth();
                double y0 = view.getRoutingCenterY(source);

                double xe = (isTargetLeft) ? target.getX() : target.getX()
                        + target.getWidth();
                double ye = view.getRoutingCenterY(target);

                double seg = segment;

                double dx = (isSourceLeft) ? -seg : seg;
                EditorPoint dep = new EditorPoint(x0 + dx, y0);
                result.add(dep);

                dx = (isTargetLeft) ? -seg : seg;
                EditorPoint arr = new EditorPoint(xe + dx, ye);

                // Adds intermediate points if both go out on same side
                if (isSourceLeft == isTargetLeft) {
                    double x = (isSourceLeft) ? Math.min(x0, xe) - segment
                            : Math.max(x0, xe) + segment;
                    result.add(new EditorPoint(x, y0));
                    result.add(new EditorPoint(x, ye));
                } else if ((dep.getX() < arr.getX()) == isSourceLeft) {
                    double midY = y0 + (ye - y0) / 2;
                    result.add(new EditorPoint(dep.getX(), midY));
                    result.add(new EditorPoint(arr.getX(), midY));
                }

                result.add(arr);
            }
        }
    };

    /**
     * Provides a self-reference, aka. loop.
     */
    public static mxEdgeStyleFunction Loop = new mxEdgeStyleFunction() {
        public void apply(CellState state, CellState source,
                          CellState target, List<EditorPoint> points, List<EditorPoint> result) {
            if (source != null) {
                GraphView view = state.getView();
                Graph graph = view.getGraph();
                EditorPoint pt = (points != null && points.size() > 0) ? points
                        .get(0) : null;

                if (pt != null) {
                    pt = view.transformControlPoint(state, pt);

                    if (source.contains(pt.getX(), pt.getY())) {
                        pt = null;
                    }
                }

                double x = 0;
                double dx = 0;
                double y = 0;
                double dy = 0;

                double seg = EditorUtils.getDouble(state.getStyle(),
                        Constants.STYLE_SEGMENT, graph.getGridSize())
                        * view.getScale();
                String dir = EditorUtils
                        .getString(state.getStyle(),
                                Constants.STYLE_DIRECTION,
                                Constants.DIRECTION_WEST);

                if (dir.equals(Constants.DIRECTION_NORTH)
                        || dir.equals(Constants.DIRECTION_SOUTH)) {
                    x = view.getRoutingCenterX(source);
                    dx = seg;
                } else {
                    y = view.getRoutingCenterY(source);
                    dy = seg;
                }

                if (pt == null || pt.getX() < source.getX()
                        || pt.getX() > source.getX() + source.getWidth()) {
                    if (pt != null) {
                        x = pt.getX();
                        dy = Math.max(Math.abs(y - pt.getY()), dy);
                    } else {
                        if (dir.equals(Constants.DIRECTION_NORTH)) {
                            y = source.getY() - 2 * dx;
                        } else if (dir.equals(Constants.DIRECTION_SOUTH)) {
                            y = source.getY() + source.getHeight() + 2 * dx;
                        } else if (dir.equals(Constants.DIRECTION_EAST)) {
                            x = source.getX() - 2 * dy;
                        } else {
                            x = source.getX() + source.getWidth() + 2 * dy;
                        }
                    }
                } else {
                    // pt != null
                    x = view.getRoutingCenterX(source);
                    dx = Math.max(Math.abs(x - pt.getX()), dy);
                    y = pt.getY();
                    dy = 0;
                }

                result.add(new EditorPoint(x - dx, y - dy));
                result.add(new EditorPoint(x + dx, y + dy));
            }
        }
    };

    /**
     * Uses either SideToSide or TopToBottom depending on the horizontal
     * flag in the cell style. SideToSide is used if horizontal is true or
     * unspecified.
     */
    public static mxEdgeStyleFunction ElbowConnector = new mxEdgeStyleFunction() {
        public void apply(CellState state, CellState source,
                          CellState target, List<EditorPoint> points, List<EditorPoint> result) {
            EditorPoint pt = (points != null && points.size() > 0) ? points.get(0)
                    : null;

            boolean vertical = false;
            boolean horizontal = false;

            if (source != null && target != null) {
                if (pt != null) {
                    double left = Math.min(source.getX(), target.getX());
                    double right = Math.max(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    double top = Math.min(source.getY(), target.getY());
                    double bottom = Math.max(
                            source.getY() + source.getHeight(), target.getY()
                            + target.getHeight());

                    pt = state.getView().transformControlPoint(state, pt);

                    vertical = pt.getY() < top || pt.getY() > bottom;
                    horizontal = pt.getX() < left || pt.getX() > right;
                } else {
                    double left = Math.max(source.getX(), target.getX());
                    double right = Math.min(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    vertical = left == right;

                    if (!vertical) {
                        double top = Math.max(source.getY(), target.getY());
                        double bottom = Math.min(
                                source.getY() + source.getHeight(),
                                target.getY() + target.getHeight());

                        horizontal = top == bottom;
                    }
                }
            }

            if (!horizontal
                    && (vertical || EditorUtils.getString(state.getStyle(),
                    Constants.STYLE_ELBOW, "").equals(
                    Constants.ELBOW_VERTICAL))) {
                EdgeStyle.TopToBottom.apply(state, source, target, points,
                        result);
            } else {
                EdgeStyle.SideToSide.apply(state, source, target, points,
                        result);
            }
        }
    };

    /**
     * Provides a vertical elbow edge.
     */
    public static mxEdgeStyleFunction SideToSide = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.kg.rails.component.editor.view.EdgeStyle.mxEdgeStyleFunction#apply(com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, java.util.List, java.util.List)
           */
        public void apply(CellState state, CellState source,
                          CellState target, List<EditorPoint> points, List<EditorPoint> result) {
            GraphView view = state.getView();
            EditorPoint pt = ((points != null && points.size() > 0) ? points.get(0)
                    : null);
            EditorPoint p0 = state.getAbsolutePoint(0);
            EditorPoint pe = state
                    .getAbsolutePoint(state.getAbsolutePointCount() - 1);

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);
            }

            if (p0 != null) {
                source = new CellState();
                source.setX(p0.getX());
                source.setY(p0.getY());
            }

            if (pe != null) {
                target = new CellState();
                target.setX(pe.getX());
                target.setY(pe.getY());
            }

            if (source != null && target != null) {
                double l = Math.max(source.getX(), target.getX());
                double r = Math.min(source.getX() + source.getWidth(),
                        target.getX() + target.getWidth());

                double x = (pt != null) ? pt.getX() : r + (l - r) / 2;

                double y1 = view.getRoutingCenterY(source);
                double y2 = view.getRoutingCenterY(target);

                if (pt != null) {
                    if (pt.getY() >= source.getY()
                            && pt.getY() <= source.getY() + source.getHeight()) {
                        y1 = pt.getY();
                    }

                    if (pt.getY() >= target.getY()
                            && pt.getY() <= target.getY() + target.getHeight()) {
                        y2 = pt.getY();
                    }
                }

                if (!target.contains(x, y1) && !source.contains(x, y1)) {
                    result.add(new EditorPoint(x, y1));
                }

                if (!target.contains(x, y2) && !source.contains(x, y2)) {
                    result.add(new EditorPoint(x, y2));
                }

                if (result.size() == 1) {
                    if (pt != null) {
                        result.add(new EditorPoint(x, pt.getY()));
                    } else {
                        double t = Math.max(source.getY(), target.getY());
                        double b = Math.min(source.getY() + source.getHeight(),
                                target.getY() + target.getHeight());

                        result.add(new EditorPoint(x, t + (b - t) / 2));
                    }
                }
            }
        }
    };

    /**
     * Provides a horizontal elbow edge.
     */
    public static mxEdgeStyleFunction TopToBottom = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.kg.rails.component.editor.view.EdgeStyle.mxEdgeStyleFunction#apply(com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, java.util.List, java.util.List)
           */
        public void apply(CellState state, CellState source,
                          CellState target, List<EditorPoint> points, List<EditorPoint> result) {
            GraphView view = state.getView();
            EditorPoint pt = ((points != null && points.size() > 0) ? points.get(0)
                    : null);
            EditorPoint p0 = state.getAbsolutePoint(0);
            EditorPoint pe = state
                    .getAbsolutePoint(state.getAbsolutePointCount() - 1);

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);
            }

            if (p0 != null) {
                source = new CellState();
                source.setX(p0.getX());
                source.setY(p0.getY());
            }

            if (pe != null) {
                target = new CellState();
                target.setX(pe.getX());
                target.setY(pe.getY());
            }

            if (source != null && target != null) {
                double t = Math.max(source.getY(), target.getY());
                double b = Math.min(source.getY() + source.getHeight(),
                        target.getY() + target.getHeight());

                double x = view.getRoutingCenterX(source);

                if (pt != null && pt.getX() >= source.getX()
                        && pt.getX() <= source.getX() + source.getWidth()) {
                    x = pt.getX();
                }

                double y = (pt != null) ? pt.getY() : b + (t - b) / 2;

                if (!target.contains(x, y) && !source.contains(x, y)) {
                    result.add(new EditorPoint(x, y));
                }

                if (pt != null && pt.getX() >= target.getX()
                        && pt.getX() <= target.getX() + target.getWidth()) {
                    x = pt.getX();
                } else {
                    x = view.getRoutingCenterX(target);
                }

                if (!target.contains(x, y) && !source.contains(x, y)) {
                    result.add(new EditorPoint(x, y));
                }

                if (result.size() == 1) {
                    if (pt != null) {
                        result.add(new EditorPoint(pt.getX(), y));
                    } else {
                        double l = Math.max(source.getX(), target.getX());
                        double r = Math.min(source.getX() + source.getWidth(),
                                target.getX() + target.getWidth());

                        result.add(new EditorPoint(l + (r - l) / 2, y));
                    }
                }
            }
        }
    };

    public static double orthBuffer = 10;

    public static double[][] dirVectors = new double[][]{{-1, 0},
            {0, -1}, {1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 0}};

    public static double[][] wayPoints1 = new double[128][2];

    /**
     * The default routing patterns for orthogonal connections
     */
    public static int[][][] routePatterns = new int[][][]{
            {{513, 2308, 2081, 2562}, {513, 1090, 514, 2184, 2114, 2561},
                    {513, 1090, 514, 2564, 2184, 2562},
                    {513, 2308, 2561, 1090, 514, 2568, 2308}},
            {{514, 1057, 513, 2308, 2081, 2562}, {514, 2184, 2114, 2561},
                    {514, 2184, 2562, 1057, 513, 2564, 2184},
                    {514, 1057, 513, 2568, 2308, 2561}},
            {{1090, 514, 1057, 513, 2308, 2081, 2562}, {2114, 2561},
                    {1090, 2562, 1057, 513, 2564, 2184},
                    {1090, 514, 1057, 513, 2308, 2561, 2568}},
            {{2081, 2562}, {1057, 513, 1090, 514, 2184, 2114, 2561},
                    {1057, 513, 1090, 514, 2184, 2562, 2564},
                    {1057, 2561, 1090, 514, 2568, 2308}}};

    /**
     * Overriden routing patterns for orthogonal connections
     * where the vertices have
     */
    public static int[][][] inlineRoutePatterns = new int[][][]{
            {null, {2114, 2568}, null, null},
            {null, {514, 2081, 2114, 2568}, null, null},
            {null, {2114, 2561}, null, null},
            {{2081, 2562}, {1057, 2114, 2568}, {2184, 2562}, null}};

    public static double[] vertexSeperations = new double[5];

    public static double[][] limits = new double[2][9];

    public static int LEFT_MASK = 32;

    public static int TOP_MASK = 64;

    public static int RIGHT_MASK = 128;

    public static int BOTTOM_MASK = 256;

    public static int LEFT = 1;

    public static int TOP = 2;

    public static int RIGHT = 4;

    public static int BOTTOM = 8;

    public static int SIDE_MASK = LEFT_MASK | TOP_MASK | RIGHT_MASK
            | BOTTOM_MASK;

    public static int CENTER_MASK = 512;

    public static int SOURCE_MASK = 1024;

    public static int TARGET_MASK = 2048;

    public static int VERTEX_MASK = SOURCE_MASK | TARGET_MASK;

    public static double vertBendProportion = 0.5;

    public static double hozBendProportion = 0.5;

    /**
     * An orthogonal connector that avoids connecting vertices and
     * respects port constraints
     */
    public static mxEdgeStyleFunction orthConnector = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.kg.rails.component.editor.view.EdgeStyle.mxEdgeStyleFunction#apply(com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, com.kg.rails.component.editor.view.CellState, java.util.List, java.util.List)
           */
        public void apply(CellState state, CellState source,
                          CellState target, List<EditorPoint> points, List<EditorPoint> result) {
            if (source != null && target != null) {
                double scaledOrthBuffer = orthBuffer
                        * state.getView().getScale();
                // Determine the side(s) of the source and target vertices
                // that the edge may connect to
                // portConstraint -> [source, target];
                int portConstraint[] = new int[2];
                portConstraint[0] = EditorUtils.getPortConstraints(source, state,
                        true);
                portConstraint[1] = EditorUtils.getPortConstraints(target, state,
                        false);

                // dir -> [source, target] initial direction leaving vertices
                int dir[] = new int[2];

                // Work out which faces of the vertices present against each other
                // in a way that would allow a 3-segment connection if port constraints
                // permitted.
                // geo -> [source, target] [x, y, width, height]
                double[][] geo = new double[2][4];
                geo[0][0] = source.getX();
                geo[0][1] = source.getY();
                geo[0][2] = source.getWidth();
                geo[0][3] = source.getHeight();

                geo[1][0] = target.getX();
                geo[1][1] = target.getY();
                geo[1][2] = target.getWidth();
                geo[1][3] = target.getHeight();

                for (int i = 0; i < 2; i++) {
                    limits[i][1] = geo[i][0] - scaledOrthBuffer;
                    limits[i][2] = geo[i][1] - scaledOrthBuffer;
                    limits[i][4] = geo[i][0] + geo[i][2] + scaledOrthBuffer;
                    limits[i][8] = geo[i][1] + geo[i][3] + scaledOrthBuffer;
                }

                // Work out which quad the target is in
                double sourceCenX = geo[0][0] + geo[0][2] / 2.0;
                double sourceCenY = geo[0][1] + geo[0][3] / 2.0;
                double targetCenX = geo[1][0] + geo[1][2] / 2.0;
                double targetCenY = geo[1][1] + geo[1][3] / 2.0;

                double dx = sourceCenX - targetCenX;
                double dy = sourceCenY - targetCenY;

                int quad = 0;

                if (dx < 0) {
                    if (dy < 0) {
                        quad = 2;
                    } else {
                        quad = 1;
                    }
                } else {
                    if (dy <= 0) {
                        quad = 3;

                        // Special case on x = 0 and negative y
                        if (dx == 0) {
                            quad = 2;
                        }
                    }

                }

                // Check for connection constraints
                EditorPoint p0 = state.getAbsolutePoint(0);
                EditorPoint pe = state.getAbsolutePoint(state
                        .getAbsolutePointCount() - 1);
                EditorPoint currentTerm = p0;

                // constraint[source, target] [x, y]
                double constraint[][] = new double[][]{{0.5, 0.5},
                        {0.5, 0.5}};

                for (int i = 0; i < 2; i++) {
                    if (currentTerm != null) {
                        constraint[i][0] = (currentTerm.getX() - geo[i][0])
                                / geo[i][2];

                        if (constraint[i][0] < 0.01) {
                            dir[i] = Constants.DIRECTION_MASK_WEST;
                        } else if (constraint[i][0] > 0.99) {
                            dir[i] = Constants.DIRECTION_MASK_EAST;
                        }

                        constraint[i][1] = (currentTerm.getY() - geo[i][1])
                                / geo[i][3];

                        if (constraint[i][1] < 0.01) {
                            dir[i] = Constants.DIRECTION_MASK_NORTH;
                        } else if (constraint[i][1] > 0.99) {
                            dir[i] = Constants.DIRECTION_MASK_SOUTH;
                        }
                    }

                    currentTerm = pe;
                }

                double sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
                double sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
                double sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
                double sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);

                vertexSeperations[1] = Math.max(sourceLeftDist - 2
                        * scaledOrthBuffer, 0);
                vertexSeperations[2] = Math.max(sourceTopDist - 2
                        * scaledOrthBuffer, 0);
                vertexSeperations[4] = Math.max(sourceBottomDist - 2
                        * scaledOrthBuffer, 0);
                vertexSeperations[3] = Math.max(sourceRightDist - 2
                        * scaledOrthBuffer, 0);

                //==============================================================
                // Start of source and target direction determination

                // Work through the preferred orientations by relative positioning
                // of the vertices and list them in preferred and available order
                int dirPref[] = new int[2];
                int horPref[] = new int[2];
                int vertPref[] = new int[2];

                horPref[0] = sourceLeftDist >= sourceRightDist ? Constants.DIRECTION_MASK_WEST
                        : Constants.DIRECTION_MASK_EAST;
                vertPref[0] = sourceTopDist >= sourceBottomDist ? Constants.DIRECTION_MASK_NORTH
                        : Constants.DIRECTION_MASK_SOUTH;

                horPref[1] = EditorUtils.reversePortConstraints(horPref[0]);
                vertPref[1] = EditorUtils.reversePortConstraints(vertPref[0]);

                double preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
                        : sourceRightDist;
                double preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
                        : sourceBottomDist;

                int prefOrdering[][] = new int[2][2];
                boolean preferredOrderSet = false;

                // If the preferred port isn't available, switch it
                for (int i = 0; i < 2; i++) {
                    if (dir[i] != 0x0) {
                        continue;
                    }

                    if ((horPref[i] & portConstraint[i]) == 0) {
                        horPref[i] = EditorUtils.reversePortConstraints(horPref[i]);
                    }

                    if ((vertPref[i] & portConstraint[i]) == 0) {
                        vertPref[i] = EditorUtils
                                .reversePortConstraints(vertPref[i]);
                    }

                    prefOrdering[i][0] = vertPref[i];
                    prefOrdering[i][1] = horPref[i];
                }

                if (preferredVertDist > scaledOrthBuffer * 2
                        && preferredHorizDist > scaledOrthBuffer * 2) {
                    // Possibility of two segment edge connection
                    if (((horPref[0] & portConstraint[0]) > 0)
                            && ((vertPref[1] & portConstraint[1]) > 0)) {
                        prefOrdering[0][0] = horPref[0];
                        prefOrdering[0][1] = vertPref[0];
                        prefOrdering[1][0] = vertPref[1];
                        prefOrdering[1][1] = horPref[1];
                        preferredOrderSet = true;
                    } else if (((vertPref[0] & portConstraint[0]) > 0)
                            && ((horPref[1] & portConstraint[1]) > 0)) {
                        prefOrdering[0][0] = vertPref[0];
                        prefOrdering[0][1] = horPref[0];
                        prefOrdering[1][0] = horPref[1];
                        prefOrdering[1][1] = vertPref[1];
                        preferredOrderSet = true;
                    }
                }
                if (preferredVertDist > scaledOrthBuffer * 2
                        && !preferredOrderSet) {
                    prefOrdering[0][0] = vertPref[0];
                    prefOrdering[0][1] = horPref[0];
                    prefOrdering[1][0] = vertPref[1];
                    prefOrdering[1][1] = horPref[1];
                    preferredOrderSet = true;

                }
                if (preferredHorizDist > scaledOrthBuffer * 2
                        && !preferredOrderSet) {
                    prefOrdering[0][0] = horPref[0];
                    prefOrdering[0][1] = vertPref[0];
                    prefOrdering[1][0] = horPref[1];
                    prefOrdering[1][1] = vertPref[1];
                    preferredOrderSet = true;
                }

                // The source and target prefs are now an ordered list of
                // the preferred port selections
                // It the list can contain gaps, compact it

                for (int i = 0; i < 2; i++) {
                    if (dir[i] != 0x0) {
                        continue;
                    }

                    if ((prefOrdering[i][0] & portConstraint[i]) == 0) {
                        prefOrdering[i][0] = prefOrdering[i][1];
                    }

                    dirPref[i] = prefOrdering[i][0] & portConstraint[i];
                    dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
                    dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
                    dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;

                    if ((dirPref[i] & 0xF) == 0) {
                        dirPref[i] = dirPref[i] << 8;
                    }
                    if ((dirPref[i] & 0xF00) == 0) {
                        dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
                    }
                    if ((dirPref[i] & 0xF0000) == 0) {
                        dirPref[i] = (dirPref[i] & 0xFFFF)
                                | ((dirPref[i] & 0xF000000) >> 8);
                    }

                    dir[i] = dirPref[i] & 0xF;

                    if (portConstraint[i] == Constants.DIRECTION_MASK_WEST
                            || portConstraint[i] == Constants.DIRECTION_MASK_NORTH
                            || portConstraint[i] == Constants.DIRECTION_MASK_EAST
                            || portConstraint[i] == Constants.DIRECTION_MASK_SOUTH) {
                        dir[i] = portConstraint[i];
                    }
                }

                //==============================================================
                // End of source and target direction determination

                int[] routePattern = getRoutePattern(dir, quad, dx, dy);

                if (dx == 0 || dy == 0) {

                }

                wayPoints1[0][0] = geo[0][0];
                wayPoints1[0][1] = geo[0][1];

                switch (dir[0]) {
                    case Constants.DIRECTION_MASK_WEST:
                        wayPoints1[0][0] -= scaledOrthBuffer;
                        wayPoints1[0][1] += constraint[0][1] * geo[0][3];
                        break;
                    case Constants.DIRECTION_MASK_SOUTH:
                        wayPoints1[0][0] += constraint[0][0] * geo[0][2];
                        wayPoints1[0][1] += geo[0][3] + scaledOrthBuffer;
                        break;
                    case Constants.DIRECTION_MASK_EAST:
                        wayPoints1[0][0] += geo[0][2] + scaledOrthBuffer;
                        wayPoints1[0][1] += constraint[0][1] * geo[0][3];
                        break;
                    case Constants.DIRECTION_MASK_NORTH:
                        wayPoints1[0][0] += constraint[0][0] * geo[0][2];
                        wayPoints1[0][1] -= scaledOrthBuffer;
                        break;
                }

                int currentIndex = 0;

                int lastOrientation = (dir[0] & (Constants.DIRECTION_MASK_EAST | Constants.DIRECTION_MASK_WEST)) > 0 ? 0
                        : 1;
                int currentOrientation = 0;

                for (int i = 0; i < routePattern.length; i++) {
                    int nextDirection = routePattern[i] & 0xF;

                    // Rotate the index of this direction by the quad
                    // to get the real direction
                    int directionIndex = nextDirection == Constants.DIRECTION_MASK_EAST ? 3
                            : nextDirection;

                    directionIndex += quad;

                    if (directionIndex > 4) {
                        directionIndex -= 4;
                    }

                    double[] direction = dirVectors[directionIndex - 1];

                    currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
                    // Only update the current index if the point moved
                    // in the direction of the current segment move,
                    // otherwise the same point is moved until there is
                    // a segment direction change
                    if (currentOrientation != lastOrientation) {
                        currentIndex++;
                        // Copy the previous way point into the new one
                        // We can't base the new position on index - 1
                        // because sometime elbows turn out not to exist,
                        // then we'd have to rewind.
                        wayPoints1[currentIndex][0] = wayPoints1[currentIndex - 1][0];
                        wayPoints1[currentIndex][1] = wayPoints1[currentIndex - 1][1];
                    }

                    boolean tar = (routePattern[i] & TARGET_MASK) > 0;
                    boolean sou = (routePattern[i] & SOURCE_MASK) > 0;
                    int side = (routePattern[i] & SIDE_MASK) >> 5;
                    side = side << quad;

                    if (side > 0xF) {
                        side = side >> 4;
                    }

                    boolean center = (routePattern[i] & CENTER_MASK) > 0;

                    if ((sou || tar) && side < 9) {
                        double limit = 0;
                        int souTar = sou ? 0 : 1;

                        if (center && currentOrientation == 0) {
                            limit = geo[souTar][0] + constraint[souTar][0]
                                    * geo[souTar][2];
                        } else if (center) {
                            limit = geo[souTar][1] + constraint[souTar][1]
                                    * geo[souTar][3];
                        } else {
                            limit = limits[souTar][side];
                        }

                        if (currentOrientation == 0) {
                            double lastX = wayPoints1[currentIndex][0];
                            double deltaX = (limit - lastX) * direction[0];

                            if (deltaX > 0) {
                                wayPoints1[currentIndex][0] += direction[0]
                                        * deltaX;
                            }
                        } else {
                            double lastY = wayPoints1[currentIndex][1];
                            double deltaY = (limit - lastY) * direction[1];

                            if (deltaY > 0) {
                                wayPoints1[currentIndex][1] += direction[1]
                                        * deltaY;
                            }
                        }
                    } else if (center) {
                        // Which center we're travelling to depend on the current direction
                        wayPoints1[currentIndex][0] += direction[0]
                                * Math.abs(vertexSeperations[directionIndex] / 2);
                        wayPoints1[currentIndex][1] += direction[1]
                                * Math.abs(vertexSeperations[directionIndex] / 2);
                    }

                    if (currentIndex > 0
                            && wayPoints1[currentIndex][currentOrientation] == wayPoints1[currentIndex - 1][currentOrientation]) {
                        currentIndex--;
                    } else {
                        lastOrientation = currentOrientation;
                    }
                }

                for (int i = 0; i <= currentIndex; i++) {
                    result.add(new EditorPoint(wayPoints1[i][0], wayPoints1[i][1]));
                }

            }
        }

        /**
         * Hook method to return the routing pattern for the given state
         * @param dir
         * @param quad
         * @param dx
         * @param dy
         * @return
         */
        protected int[] getRoutePattern(int[] dir, double quad, double dx,
                                        double dy) {
            int sourceIndex = dir[0] == Constants.DIRECTION_MASK_EAST ? 3
                    : dir[0];
            int targetIndex = dir[1] == Constants.DIRECTION_MASK_EAST ? 3
                    : dir[1];

            sourceIndex -= quad;
            targetIndex -= quad;

            if (sourceIndex < 1) {
                sourceIndex += 4;
            }
            if (targetIndex < 1) {
                targetIndex += 4;
            }

            int[] result = routePatterns[sourceIndex - 1][targetIndex - 1];

            if (dx == 0 || dy == 0) {
                if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null) {
                    result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
                }
            }

            return result;
        }
    };
}
